-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local log = require 'mc.logging'
local utils = require 'mc.utils'
local custom_messages = require 'messages.custom'
local base_messages = require 'messages.base'
local remote = require 'canbus.remote_access'
local mdb_service = require 'mc.mdb.mdb_service'
local definition = require 'canbus.definition'

local debug_handler = {}

local function parse_target(target)
    local items = utils.split(target, ':')
    if not items or #items < 2 then
        log:error('target [%s] does not contains valid protocol', target)
        return false
    end
    local protocol = items[1]
    if protocol ~= 'TCP' and protocol ~= 'CAN' then
        log:error('target [%s] contains an unsupported protocol')
        return false
    end

    items = utils.split(items[2], '/')
    if not items or #items < 2 then
        log:error('target [%s] does not contains valid address', target)
        return false
    end
    local address = tonumber(items[1])
    if not address or address < 0 or address > 63 then  -- 有效目标标识为0到63
        log:error('address [%s] is out of range', address)
        return false
    end

    local channel = tonumber(items[2])
    if not channel or (protocol == 'TCP' and channel <= 0) or (protocol == 'CAN' and channel > 1) then
        log:error('target [%s] contains an unsupported channel', target)
        return false
    end

    return true, protocol, address, channel
end

-- 校验入参是否合法，object和interface必选，所以根据是否有properties判断校验属性还是接口
local function check_remote_param_valid(object, interface, properties, method, signature, args)
    -- 对象名不能为空
    if object == '' then
        return false, 'Object'
    end

    if interface == '' or not string.find(interface, 'bmc%.kepler%.') then
        return false, 'Interface'
    end

    local flag = false  -- 默认为false，可以覆盖入参空表的场景

    if properties then
        -- 检测是否有空属性
        for _, v in pairs(properties) do
            if v == '' then
                flag = false
                break
            end
            flag = true
        end

        if not flag then
            return false, 'Properties'
        else
            return true
        end
    end

    if method == '' then
        return false, 'Method'
    end

    if signature == '' or string.sub(signature, 1, 5) ~= 'a{ss}' then  -- 第1到5个字节固定
        return false, 'Signature'
    end

    return true
end

-- 根据对象名解析service和path
local function get_object_service_path(bus, object)
    local ok, res = pcall(mdb_service.get_object_owner, bus, object)
    if not ok then
        log:error('get service name of [%s] failed, [%s]', object, res)
        error(custom_messages.PropertyValueError('Object'))
    end

    return res.ObjectOwners[1].Service, res.ObjectOwners[1].Path
end


function debug_handler.get_remote_properties(bus, target, object, interface, properties)
    -- 转换target
    local ok, protocol, address, channel = parse_target(target)
    if not ok then
        return false
    end
    log:notice('get remote properties via [protocol: %s, addr: %s, chan: %s]', protocol, address, channel)
    -- TODO鉴权
    -- 校验是否合法
    local res
    ok, res = check_remote_param_valid(object, interface, properties)
    if not ok then
        log:error('get remote property failed, invalid [%s]', res)
        error(custom_messages.PropertyValueError(res))
    end

    local service, path = get_object_service_path(bus, object)
    local code, response = remote.get_prop(channel, address, service, path, interface, properties)
    if code ~= definition.remote_access_errors.OK then
        log:error('get remote property failed, code: [%s]', code)
        error(base_messages.InternalError())
    end
    return response
end

function debug_handler.call_remote_method(bus, target, object, interface, method, signature, args)
    local ok, protocol, address, channel = parse_target(target)
    if not ok then
        return false
    end

    local res
    ok, res = check_remote_param_valid(object, interface, nil, method, signature, args)
    if not ok then
        log:error('call remote method failed, invalid [%s]', res)
        error(custom_messages.PropertyValueError(res))
    end
    log:notice('call remote method via [protocol: %s, addr: %s, chan: %s]', protocol, address, channel)

    local service, path = get_object_service_path(bus, object)
    local code, response = remote.call(channel, address, service, path, interface, method, signature, args)
    if code ~= definition.remote_access_errors.OK then
        log:error('call remote method failed, code: [%s]', code)
        error(base_messages.InternalError())
    end
    return response
end

return debug_handler